/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.optimus.bo.impl;

import com.optimus.bo.RutaOptimaBO;
import com.optimus.common.cvo.ElementoPublicitarioCVO;
import com.optimus.common.cvo.ElementoPublicitarioCVOList;
import com.optimus.common.util.ApplicationException;
import com.optimus.common.util.ConstantesDB;
import com.optimus.common.util.Constants;
import com.optimus.common.vo.BaseVO;
import com.optimus.common.vo.CampanaVO;
import com.optimus.common.vo.DistanciaVO;
import com.optimus.common.vo.MercadoVO;
import com.optimus.db.dao.DistanciaDAO;
import com.optimus.db.dao.impl.DistanciaDAOImpl;
import com.optimus.external.Distance;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author vperez
 */
public class RutaOptimaBOImpl implements RutaOptimaBO{

    private static  Logger logger = LoggerFactory.getLogger(RutaOptimaBOImpl.class);

    DistanciaDAO distanciaDAO = new DistanciaDAOImpl();

    public BaseVO calcularRutaOptima(CampanaVO campanaVO, MercadoVO mercadoVO,ElementoPublicitarioCVOList epvo) throws ApplicationException {
        BaseVO resultado= new BaseVO();
        Object[] logParams = new Object[2];
        Long tk =campanaVO.getTicket();
        resultado.setCodigo(ConstantesDB.STATUS_FAIL);
        resultado.setTicket(tk);
        try {
             List<ElementoPublicitarioCVO> lista= epvo.getLista();
             Integer inicio=0;
             Integer termino=0;
             BigDecimal distancia=BigDecimal.ZERO;
             int cantidad = lista.size();
             int longitudMatrizD=  cantidad*(cantidad-1)/2 ;
             int longitudMatriz= (int)longitudMatrizD;
             if(longitudMatriz==0){
                 logger.debug("Longitud es cero");
             }
             DistanciaVO [] distancias= new DistanciaVO[longitudMatriz];
             int contador =0;

             //Vector de inicio de distincias para un determinado punto
             long[][] matriz= new long[cantidad][cantidad];
             
             //Vector de ubicacion del EP en los indices
             HashMap<Integer,Integer> posicion = new HashMap<Integer,Integer> ();
             HashMap<Integer,Integer> posicionReversa = new HashMap<Integer,Integer> ();
             int[] path = new int[cantidad];
             logger.debug("msg = mapeando indices a elementos publicitarios ticket = " + tk);
             for (int idx=0; idx<lista.size();idx++ ) {
                 termino=lista.get(idx).getIdElementoPublicitario();
                 posicion.put(termino, idx);
                 posicionReversa.put(idx,termino);
                 logger.debug(" ticket = " + tk + " termino= " + termino.toString()+ " indice="+ idx);
             }

             termino=0;
             logger.debug("calculando distancias entre puntos ticket= " + tk);
             logger.debug("cantidad de elementos = "+lista.size()+" ticket= " + tk);
             logger.debug("cantidad de distancias a calcular"+distancias.length);
             for (int idx=0; idx<lista.size();idx++ ) {
                inicio=lista.get(idx).getIdElementoPublicitario();                
                 for (int idy=idx+1; idy<lista.size();idy++ ) {
                     logger.debug(" ticket = " + tk+" iteracion = " + idx +" subiteracion = "+idy+ " ep = " + inicio);
                     termino=lista.get(idy).getIdElementoPublicitario();
                     logger.debug(" termino = " + termino);
                     distancia=calcularDistancia(lista.get(idx),lista.get(idy));
                     logger.debug(" distancia = " + distancia);
                     if(distancia==null){
                         logger.debug("ES NULO: ticket = " + tk+" iteracion = " + idx + " ep-ini = " + inicio + " ep-term = " + termino);
                     }
                     logger.debug("ticket = " + tk + " c = " + contador);
                     distancias[contador++]=new DistanciaVO(inicio, termino,distancia , "M");
                     long valueLong=distancia.multiply(new BigDecimal(Constants.ESCALA)).longValue();//Convertir a un entero

                     matriz[(int)posicion.get(inicio)][(int)posicion.get(termino)]=valueLong;
                     matriz[(int)posicion.get(termino)][(int)posicion.get(inicio)]=valueLong;
                 }
             }

             StringBuffer sb = new StringBuffer();
             for(int i=0;i<matriz.length;i++){
                 for(int j=0;j<matriz.length;j++){
                     sb.append("*").append(matriz[i][j]);
                 }
                 sb.append("|");
             }
             logger.debug("distancias: ");
             logger.debug( sb.toString());
             logger.debug("Cantidad de distancias calculadas" + contador);
             logger.debug("cantidad de distancias a calcular"+distancias.length);
             logger.debug("guardando distancias calculadas ticket = " + tk);
             guardarDistancias(distancias,resultado);
             long distanciaMin = Long.MAX_VALUE;
             int  destino=0;
             int  startEP=lista.get(0).getIdElementoPublicitario();//El punto desde el cual se inicia el calculo de la ruta minima
             int  start=(int)posicion.get(startEP);//Es el indice de la posicion en el orden de la lista
             int  idx=start;
             int  it =0;
             path[it++]=startEP;
             boolean[] visitado= new boolean[cantidad];
             visitado[idx]=true;
             logger.debug("iniciando calculo de la ruta minima ticket = " + tk);
             while(idx<matriz.length){
                 distanciaMin = Long.MAX_VALUE;
                 for (int idy=0; idy<matriz.length;idy++ ) {
                   if(idx==idy){
                       continue;
                   }
                   if(matriz[idx][idy]>0&&matriz[idx][idy]<distanciaMin&&visitado[idy]==false){
                       distanciaMin=matriz[idx][idy];
                       destino=idy;
                   }
                 }
                 logger.debug("origen: "+idx+" destino: " + destino);
                 path[it++]=posicionReversa.get(destino);
                 visitado[destino]=true;
                 idx=destino;
                 int cantidadVisitados=0;
                 for(int idz=0;idz<visitado.length;idz++){
                     if(visitado[idz]){
                         cantidadVisitados++;
                     }
                 }
                 if(cantidadVisitados>=visitado.length){
                     break;
                 }
                 if(idx>=matriz.length){
                     break;
                 }
                 if(it>=posicionReversa.size()){
                     break;
                 }
             }
             for (ElementoPublicitarioCVO item : lista) {
                for(int i=0;i<path.length;i++){
                    if(path[i]==item.getIdElementoPublicitario()){
                        item.setOrden(i+1);
                        break;
                    }
                }
             }
             resultado.setCodigo(ConstantesDB.STATUS_OK);
        } catch (Exception e) {
            e.printStackTrace();
            resultado.setCodigo(ConstantesDB.STATUS_FAIL);
        }
        return resultado;
    }

    protected BigDecimal calcularDistancia(ElementoPublicitarioCVO inicio,ElementoPublicitarioCVO termino){
        BigDecimal res= new BigDecimal(0);
        try {
            char unit=Constants.CALCULO_EN_KM;
            double lat1=inicio.getLatitud().doubleValue();
            double lon1=inicio.getLongitud().doubleValue();
            double lat2=termino.getLatitud().doubleValue();
            double lon2=termino.getLongitud().doubleValue();
            double resultado=Distance.i().distance(lat1, lon1, lat2, lon2, unit);
            resultado=resultado*Constants.PONDERACION_METROS;//Convertirlo a metros
            res= new BigDecimal(resultado);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return res;
    }

    protected void guardarDistancias(DistanciaVO [] distancias, BaseVO baseVO ) throws ApplicationException {
        try {

            distanciaDAO.borrarDistancias(baseVO);
            for (DistanciaVO distanciaVO : distancias) {
                if(distanciaVO==null){
                    logger.debug("no hay datos a guardar");
                    continue;
                }
                distanciaDAO.insertar(distanciaVO);
            }
        } catch (Exception e) {
            logger.debug("Hubo error al guardar", e);
        }
    }
}
